/*file chiron src/jackslocum.com/select.js */
/*preamble

    Copyright (c) 2002-2008 Kris Kowal <http://cixar.com/~kris.kowal>
    MIT License
    
    The license terms are stated in full in <license.rst> and at the end
    of all source files.

*/
/*

    Ext - JS Library 1.0 Alpha 3 - Rev 4
    Copyright(c) 2006-2007, Jack Slocum.
    
    http://www.extjs.com/license.txt

    This is code is also distributed under MIT license for use
    with jQuery and Prototype JavaScript libraries.

*/

/**
    Provides a XML (CSS) selector and XPath implementation.
*/

/*status requires extensive testing */

include('browser.js');
include('environment.js');
include('base.js');

/*** selectArray
    Selects a group of elements using XPath or XML selectors, usually
    called CSS selectors.

    accepts:
     - ``String`` selector.  The selector or XPath query.
     - ``Node`` an optional root element to start the query on.
        defaults to the ``document``.  ``Node`` may be a 
        ``String``, in which case it will be used to fetch
        the corresponding DOM node by ID.

    returns an `Array` of Nodes.
*/
exports.selectArray = function (path, root) {
    if (!root || root == document) {
        root = document;
    }
    if (typeof root == "string") {
        root = document.getElementById(root);
    }
    var paths = path.split(",");
    var results = [];
    for (var i = 0, len = paths.length; i < len; i++) {
        var p = paths[i].replace(trimRe, "$1");
        if (!cache[p]) {
            cache[p] = compile(p);
            if (!cache[p]) {
                throw new Error(p + " is not a valid selector");
            }
        }
        var result = cache[p](root);
        if (result && result != document) {
            results = results.concat(result);
        }
    }
    return results;
};

/*** select
    Selects a group of elements using XPath or XML selectors, usually
    called CSS selectors.

    accepts:
     - ``String`` selector.  The selector or XPath query.
     - ``Node`` an optional root element to start the query on.
        defaults to the ``document``.  ``Node`` may be a 
        ``String``, in which case it will be used to fetch
        the corresponding DOM node by ID.

    returns an `List` of Nodes.
*/
exports.select = to(selectArray, List);

/*** compile
    compiles a selector or XPath query into a reusable function.

    accepts:
     - a selector or XPath `String`
     - optionally one of
       - ``"select"`` by default, or
       - ``"simple"`` for a simple selector match

    The returned function takes one optional parameter, "root",
    that is the context node.
*/
exports.compile = function (path, type) {
    // strip leading slashes
    while(path.substr(0, 1)=="/") {
        path = path.substr(1);
    }
    type = type || "select";
    
    var fn = ["function f(root) {\n var mode; var n = root || document;\n"];
    var q = path, mode, lq;
    var tk = matchers;
    var tklen = tk.length;
    var mm;
    while(q && lq != q) {
        lq = q;
        var tm = q.match(tagTokenRe);
        if (type == "select") {
            if (tm) {
                if (tm[1] == "#") {
                    fn[fn.length] = 'n = quickId(n, mode, root, "'+tm[2]+'");';
                }else{
                    fn[fn.length] = 'n = getNodes(n, mode, "'+tm[2]+'");';
                }
                q = q.replace(tm[0], "");
            }else if (q.substr(0, 1) != '@') {
                fn[fn.length] = 'n = getNodes(n, mode, "*");';
            }
        }else{
            if (tm) {
                if (tm[1] == "#") {
                    fn[fn.length] = 'n = byId(n, null, "'+tm[2]+'");';
                }else{
                    fn[fn.length] = 'n = byTag(n, "'+tm[2]+'");';
                }
                q = q.replace(tm[0], "");
            }
        }
        while(!(mm = q.match(modeRe))) {
            var matched = false;
            for (var j = 0; j < tklen; j++) {
                var t = tk[j];
                var m = q.match(t.re);
                if (m) {
                    fn[fn.length] = t.select.replace(tplRe, function(x, i) {
                                            return m[i];
                                        });
                    q = q.replace(m[0], "");
                    matched = true;
                    break;
                }
            }
            // prevent infinite loop on bad selector
            if (!matched) {
                throw new Error('Error parsing selector, parsing failed at "' + q + '"');
            }
        }
        if (mm[1]) {
            fn[fn.length] = 'mode="'+mm[1]+'";';
            q = q.replace(mm[1], "");
        }
    }
    fn[fn.length] = "return nodup(n);\n}";
    eval(fn.join(""));
    return f;
};

var cache = {}, simpleCache = {}, valueCache = {};
var nonSpace = /\S/;
var trimRe = /^\s*(.*?)\s*$/;
var tplRe = /\{(\d+)\}/g;
var modeRe = /^(\s?[\/>]\s?|\s|$)/;
var tagTokenRe = /^(#)?([\w-\*]+)/;

function child(p, index) {
    var i = 0;
    var n = p.firstChild;
    while(n) {
        if (n.nodeType == 1) {
           if (++i == index) {
               return n;
           }
        }
        n = n.nextSibling;
    }
    return null;
}

function next(n) {
    while((n = n.nextSibling) && n.nodeType != 1);
    return n;
}

function prev(n) {
    while((n = n.previousSibling) && n.nodeType != 1);
    return n;
}

function clean(d) {
    var n = d.firstChild, ni = -1;
    while(n) {
        var nx = n.nextSibling;
        if (n.nodeType == 3 && !nonSpace.test(n.nodeValue)) {
            d.removeChild(n);
        }else{
            n.nodeIndex = ++ni;
        }
        n = nx;
    }
    return this;
}

function byClassName(c, a, v, re, cn) {
    if (!v) {
        return c;
    }
    var r = [];
    for (var i = 0, ci; ci = c[i]; i++) {
        cn = ci.className;
        if (cn && (' '+cn+' ').indexOf(v) != -1) {
            r[r.length] = ci;
        }
    }
    return r;
}

function attrValue(n, attr) {
    if (!n.tagName && typeof n.length != "undefined") {
        n = n[0];
    }
    if (!n) {
        return null;
    }
    if (attr == "for") {
        return n.htmlFor;
    }
    if (attr == "class" || attr == "className") {
        return n.className;
    }
    return n.getAttribute(attr) || n[attr];
      
}

function getNodes(ns, mode, tagName) {
    var result = [], cs;
    if (!ns) {
        return result;
    }
    mode = mode ? mode.replace(trimRe, "$1") : "";
    tagName = tagName || "*";
    if (typeof ns.getElementsByTagName != "undefined") {
        ns = [ns];   
    }
    if (mode != "/" && mode != ">") {
        for (var i = 0, ni; ni = ns[i]; i++) {
            cs = ni.getElementsByTagName(tagName);
            for (var j = 0, ci; ci = cs[j]; j++) {
                result[result.length] = ci;
            }
        }
    }else{
        for (var i = 0, ni; ni = ns[i]; i++) {
            var cn = ni.getElementsByTagName(tagName);
            for (var j = 0, cj; cj = cn[j]; j++) {
                if (cj.parentNode == ni) {
                    result[result.length] = cj;
                }
            }
        }
    }
    return result;
}

function concat(a, b) {
    if (b.slice) {
        return a.concat(b);
    }
    for (var i = 0, l = b.length; i < l; i++) {
        a[a.length] = b[i];
    }
    return a;
}

function byTag(cs, tagName) {
    if (cs.tagName || cs == document) {
        cs = [cs];
    }
    if (!tagName) {
        return cs;
    }
    var r = []; tagName = tagName.toLowerCase();
    for (var i = 0, ci; ci = cs[i]; i++) {
        if (ci.nodeType == 1 && ci.tagName.toLowerCase()==tagName) {
            r[r.length] = ci;
        }
    }
    return r; 
}

function byId(cs, attr, id) {
    if (cs.tagName || cs == document) {
        cs = [cs];
    }
    if (!id) {
        return cs;
    }
    var r = [];
    for (var i = 0,ci; ci = cs[i]; i++) {
        if (ci && ci.id == id) {
            r[r.length] = ci;
            return r;
        }
    }
    return r; 
}

function byAttribute(cs, attr, value, op, custom) {
    var r = [], st = custom=="{";
    var f = operators[op];
    for (var i = 0; ci = cs[i]; i++) {
        var a;
        if (st) {
            a = getStyle(ci, attr);
        }
        else if (attr == "class" || attr == "className") {
            a = ci.className;
        }else if (attr == "for") {
            a = ci.htmlFor;
        }else if (attr == "href") {
            a = ci.getAttribute("href", 2);
        }else{
            a = ci.getAttribute(attr);
        }
        if ((f && f(a, value)) || (!f && a)) {
            r[r.length] = ci;
        }
    }
    return r;
}

function byPseudo(cs, name, value) {
    return pseudos[name](cs, value);
}

var key = 30803;

/*
 * Selects a single element.
 * @param {String} selector The selector/xpath query
 * @param {Node} root (optional) The start of the query (defaults to document).
 * @return {Element}
 */
function selectNode(path, root) {
    return select(path, root)[0];
};

/*
 * Selects the value of a node, optionally replacing null with the defaultValue.
 * @param {String} selector The selector/xpath query
 * @param {Node} root (optional) The start of the query (defaults to document).
 * @param {String} defaultValue
 */
function selectValue(path, root, defaultValue) {
    path = path.replace(trimRe, "$1");
    if (!valueCache[path]) {
        valueCache[path] = compile(path, "select");
    }
    var n = valueCache[path](root);
    n = n[0] ? n[0] : n;
    var v = (n && n.firstChild ? n.firstChild.nodeValue : null);
    return (v === null ? defaultValue : v);
};

/*
 * Selects the value of a node, parsing integers and floats.
 * @param {String} selector The selector/xpath query
 * @param {Node} root (optional) The start of the query (defaults to document).
 * @param {Number} defaultValue
 * @return {Number}
 */
function selectNumber(path, root, defaultValue) {
    var v = selectValue(path, root, defaultValue || 0);
    return parseFloat(v);
};

/*
 * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
 * @param {String/HTMLElement/Array} el An element id, element or array of elements
 * @param {String} selector The simple selector to test
 * @return {Boolean}
 */
function is(el, ss) {
    if (typeof el == "string") {
        el = document.getElementById(el);
    }
    var isArray = (el instanceof Array);
    var result = filter(isArray ? el : [el], ss);
    return isArray ? (result.length == el.length) : (result.length > 0);
};

/*
 * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
 * @param {Array} el An array of elements to filter
 * @param {String} selector The simple selector to test
 * @param {Boolean} nonMatches If true, it returns the elements that DON'T match 
 * the selector instead of the ones that match
 * @return {Array}
 */
function filter(els, ss, nonMatches) {
    ss = ss.replace(trimRe, "$1");
    if (!simpleCache[ss]) {
        simpleCache[ss] = compile(ss, "simple");
    }
    var result = simpleCache[ss](els);
    return nonMatches ? quickDiff(result, els) : result;
};

/*
 * Collection of matching regular expressions and code snippets. 
 */
var matchers = [
    {
        re: /^\.([\w-]+)/,
        select: 'n = byClassName(n, null, " {1} ");'
    }, {
        re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
        select: 'n = byPseudo(n, "{1}", "{2}");'
    }, {
        re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
        select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
    }, {
        re: /^#([\w-]+)/,
        select: 'n = byId(n, null, "{1}");'
    }, {
        re: /^@([\w-]+)/,
        select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
    }
];

/*
 * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *= and %=.
 * New operators can be added as long as the match the format <i>c</i>= where <i>c<i> is any character other than space, &gt; &lt;.
 */
var operators = {
    "=" : function(a, v) {
        return a == v;
    },
    "!=" : function(a, v) {
        return a != v;
    },
    "^=" : function(a, v) {
        return a && a.substr(0, v.length) == v;
    },
    "$=" : function(a, v) {
        return a && a.substr(a.length-v.length) == v;
    },
    "*=" : function(a, v) {
        return a && a.indexOf(v) !== -1;
    },
    "%=" : function(a, v) {
        return (a % v) == 0;
    }
};

/*
 * Collection of "pseudo class" processors. Each processor is passed the current nodeset (array)
 * and the argument (if any) supplied in the selector.
 */
var pseudos = {
    "first-child" : function(c) {
        var r = [], n;
        for (var i = 0, ci; ci = n = c[i]; i++) {
            while((n = n.previousSibling) && n.nodeType != 1);
            if (!n) {
                r[r.length] = ci;
            }
        }
        return r;
    },
    
    "last-child" : function(c) {
        var r = [];
        for (var i = 0, ci; ci = n = c[i]; i++) {
            while((n = n.nextSibling) && n.nodeType != 1);
            if (!n) {
                r[r.length] = ci;
            }
        }
        return r;
    },
    
    "nth-child" : function(c, a) {
        var r = [];
        if (a != "odd" && a != "even") {
            for (var i = 0, ci; ci = c[i]; i++) {
                var m = child(ci.parentNode, a);
                if (m == ci) {
                    r[r.length] = m;
                }
            }
            return r;
        }
        var p;
        // first let's clean up the parent nodes
        for (var i = 0, l = c.length; i < l; i++) {
            var cp = c[i].parentNode;
            if (cp != p) {
                clean(cp);
                p = cp;
            }
        }
        // then lets see if we match
        for (var i = 0, ci; ci = c[i]; i++) {
            var m = false;
            if (a == "odd") {
                m = ((ci.nodeIndex+1) % 2 == 1);
            }else if (a == "even") {
                m = ((ci.nodeIndex+1) % 2 == 0);
            }
            if (m) {
                r[r.length] = ci;
            }
        }
        return r;
    },
    
    "only-child" : function(c) {
        var r = [];
        for (var i = 0, ci; ci = c[i]; i++) {
            if (!prev(ci) && !next(ci)) {
                r[r.length] = ci;
            }
        }
        return r;
    },
    
    "empty" : function(c) {
        var r = [];
        for (var i = 0, ci; ci = c[i]; i++) {
            var cns = ci.childNodes, j = 0, cn, empty = true;
            while(cn = cns[j]) {
                ++j;
                if (cn.nodeType == 1 || cn.nodeType == 3) {
                    empty = false;
                    break;
                }
            }
            if (empty) {
                r[r.length] = ci;
            }
        }
        return r;
    },
    
    "contains" : function(c, v) {
        var r = [];
        for (var i = 0, ci; ci = c[i]; i++) {
            if (ci.innerHTML.indexOf(v) !== -1) {
                r[r.length] = ci;
            }
        }
        return r;
    },

    "nodeValue" : function(c, v) {
        var r = [];
        for (var i = 0, ci; ci = c[i]; i++) {
            if (ci.firstChild && ci.firstChild.nodeValue == v) {
                r[r.length] = ci;
            }
        }
        return r;
    },

    "checked" : function(c) {
        var r = [];
        for (var i = 0, ci; ci = c[i]; i++) {
            if (ci.checked == true) {
                r[r.length] = ci;
            }
        }
        return r;
    },
    
    "not" : function(c, ss) {
        return filter(c, ss, true);
    },
    
    "odd" : function(c) {
        return this["nth-child"](c, "odd");
    },
    
    "even" : function(c) {
        return this["nth-child"](c, "even");
    },
    
    "nth" : function(c, a) {
        return c[a-1];
    },
    
    "first" : function(c) {
        return c[0];
    },
    
    "last" : function(c) {
        return c[c.length-1];
    },
    
    "has" : function(c, ss) {
        var s = select;
        var r = [];
        for (var i = 0, ci; ci = c[i]; i++) {
            if (s(ss, ci).length > 0) {
                r[r.length] = ci;
            }
        }
        return r;
    },
    
    "next" : function(c, ss) {
        var r = [];
        for (var i = 0, ci; ci = c[i]; i++) {
            var n = next(ci);
            if (n && is(n, ss)) {
                r[r.length] = ci;
            }
        }
        return r;
    },
    
    "prev" : function(c, ss) {
        var r = [];
        for (var i = 0, ci; ci = c[i]; i++) {
            var n = prev(ci);
            if (n && is(n, ss)) {
                r[r.length] = ci;
            }
        }
        return r;
    }

}

function nodupIEXml(cs) {
    var d = ++key;
    cs[0].setAttribute("_nodup", d);
    var r = [cs[0]];
    for (var i = 1, len = cs.length; i < len; i++) {
        var c = cs[i];
        if (!c.getAttribute("_nodup") != d) {
            c.setAttribute("_nodup", d);
            r[r.length] = c;
        }
    }
    for (var i = 0, len = cs.length; i < len; i++) {
        cs[i].removeAttribute("_nodup");
    }
    return r;
}

function nodup(cs) {
    var len = cs.length, c, i, r = cs, cj;
    if (!len || typeof cs.nodeType != "undefined" || len == 1) {
        return cs;
    }
    if (isIE && typeof cs[0].selectSingleNode != "undefined") {
        return nodupIEXml(cs);
    }
    var d = ++key;
    cs[0]._nodup = d;
    for (i = 1; c = cs[i]; i++) {
        if (c._nodup != d) {
            c._nodup = d;
        }else{
            r = [];
            for (var j = 0; j < i; j++) {
                r[r.length] = cs[j];
            }
            for (j = i+1; cj = cs[j]; j++) {
                if (cj._nodup != d) {
                    cj._nodup = d;
                    r[r.length] = cj;
                }
            }
            return r;
        }
    }
    return r;
}

function quickDiffIEXml(c1, c2) {
    var d = ++key;
    for (var i = 0, len = c1.length; i < len; i++) {
        c1[i].setAttribute("_qdiff", d);
    }
    var r = [];
    for (var i = 0, len = c2.length; i < len; i++) {
        if (c2[i].getAttribute("_qdiff") != d) {
            r[r.length] = c2[i];
        }
    }
    for (var i = 0, len = c1.length; i < len; i++) {
       c1[i].removeAttribute("_qdiff");
    }
    return r;
}

function quickDiff(c1, c2) {
    var len1 = c1.length;
    if (!len1) {
        return c2;
    }
    if (isIE && c1[0].selectSingleNode) {
        return quickDiffIEXml(c1, c2);
    }
    var d = ++key;
    for (var i = 0; i < len1; i++) {
        c1[i]._qdiff = d;
    }
    var r = [];
    for (var i = 0, len = c2.length; i < len; i++) {
        if (c2[i]._qdiff != d) {
            r[r.length] = c2[i];
        }
    }
    return r;
}

function quickId(ns, mode, root, id) {
    if (ns == root) {
       var d = root.ownerDocument || root;
       return d.getElementById(id);
    }
    ns = getNodes(ns, mode, "*");
    return byId(ns, null, id);
}


/*license

    Legal
    =======
    
    Chiron is a component of the Tale web-game project.
    
    See <credit.txt> for a complete list of
    contributions and their licenses.  All contributions are provided
    under permissive, non-viral licenses including MIT, BSD, Creative Commons
    Attribution 2.5, Public Domain, or Unrestricted.
    
    
    License
    =======
    
    Copyright (c) 2002-2008 Kris Kowal <http://cixar.com/~kris.kowal>
    MIT License
    
    
    MIT License
    -----------
    
    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:
    
    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.

*/

